use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::AttrStyle;
use syn::Attribute;
use syn::GenericParam;
use syn::Generics;
use syn::PredicateType;
use syn::Result;
use syn::TypeParamBound;
use syn::WherePredicate;
use syn::bracketed;
use syn::parse::ParseStream;
use syn::punctuated::Punctuated;
use syn::{GenericArgument, PathArguments, Token, Type, WhereClause};

pub fn path_to_string(path: &syn::Path) -> String {
    path.segments
        .iter()
        .map(|s| s.ident.to_string())
        .collect::<Vec<String>>()
        .join("::")
}

pub fn type_is_option(ty: &Type) -> bool {
    type_of_option(ty).is_some()
}

pub fn type_of_option(ty: &Type) -> Option<&Type> {
    let path = match ty {
        Type::Path(ty) => &ty.path,
        _ => return None,
    };

    let last = path.segments.last().unwrap();
    if last.ident != "Option" {
        return None;
    }

    let bracketed = match &last.arguments {
        PathArguments::AngleBracketed(bracketed) => bracketed,
        _ => return None,
    };

    if bracketed.args.len() != 1 {
        return None;
    }

    match &bracketed.args[0] {
        GenericArgument::Type(arg) => Some(arg),
        _ => None,
    }
}

pub fn type_add_colon2(ret: &mut Type) {
    if let Type::Path(p) = ret {
        let path = &mut p.path;
        let segs = &mut path.segments;
        for seg in segs {
            if let PathArguments::AngleBracketed(ab) = &mut seg.arguments {
                if ab.colon2_token.is_none() {
                    ab.colon2_token = Some(<Token![::]>::default());
                }
            }
        }
    }
}

pub fn append_where_generics_bounds(
    where_generics: &mut WhereClause,
    constraint: TokenStream,
) -> Result<()> {
    let mut add_predicate: Punctuated<WherePredicate, Token![,]> = Punctuated::new();
    for item in &where_generics.predicates {
        if let WherePredicate::Type(predicate_type) = item {
            let mut bounds: Punctuated<TypeParamBound, Token![+]> = Punctuated::new();
            bounds.push(TypeParamBound::Verbatim(constraint.clone()));
            add_predicate.push(WherePredicate::Type(PredicateType {
                lifetimes: None,
                bounded_ty: predicate_type.bounded_ty.clone(),
                colon_token: predicate_type.colon_token,
                bounds,
            }));
        }
    }
    where_generics.predicates.extend(add_predicate);
    Ok(())
}

pub fn add_where_generics_bounds(generics: &mut Generics, constraint: &TokenStream) -> Result<()> {
    let mut add_predicate: Punctuated<WherePredicate, Token![,]> = Punctuated::new();
    for item in &generics.params {
        if let GenericParam::Type(ty) = item {
            let mut bounds: Punctuated<TypeParamBound, Token![+]> = Punctuated::new();
            bounds.push(TypeParamBound::Verbatim(constraint.clone()));
            add_predicate.push(WherePredicate::Type(PredicateType {
                lifetimes: None,
                bounded_ty: Type::Verbatim(ty.ident.to_token_stream()),
                colon_token: <Token![:]>::default(),
                bounds,
            }));
        }
    }
    let where_clause = generics.make_where_clause();
    where_clause.predicates.extend(add_predicate);
    Ok(())
}

pub struct AttributeWrapper(pub Attribute);

impl syn::parse::Parse for AttributeWrapper {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        let inner = Attribute {
            pound_token: input.parse()?,
            style: AttrStyle::Outer,
            bracket_token: bracketed!(content in input),
            meta: content.parse()?,
        };
        Ok(Self(inner))
    }
}
